from strange_io import SIGNATURE, get_format, _load_bytearray

from strange_io.text import StrangeText
from strange_io.integer import StrangeInteger
from strange_io.float import StrangeFloat
from strange_io.array import StrangeArray
from strange_io.dictionary import StrangeDictionary

class StrangeObject:
    """ Relient on other Strange I/O formats """

    def __init__(self):
        self.data: bytes = b''

    def dump(self) -> dict:
        """ Get decoded object """
        return self.decode(self.data)

    def load(self, data: dict):
        """ Encode and store object """
        self.data = self.encode(data)

    def dump_bytes(self) -> bytes:
        """ Get encoded object """
        return self.data

    def load_bytes(self, data: bytes):
        """ Store encoded object """
        self.data = data

    def dump_file(self, path):
        """ Dump object to file """
        out_file = bytearray()
        header = bytearray(SIGNATURE + b'object\n')
        out_file += header
        out_file += self.data
        with open(path, 'wb') as f:
            f.write(out_file)
            f.close()

    def load_file(self, path, ignore_type=False):
        """ Load object from file """
        if get_format(path) == b'object' or ignore_type:
            file = _load_bytearray(path, remove_type=True)
            out = file
            self.data = out
        else:
            raise TypeError('Attempted to load non-object document as object document')

    @staticmethod
    def encode(data: dict) -> bytes:
        dictionary = StrangeDictionary()
        processed_data = StrangeObject.proccess_dict_encode(data)
        dictionary.load(processed_data)
        out = dictionary.dump_bytes()
        return out

    @staticmethod
    def proccess_dict_encode(data: dict) -> dict:
        out = {}
        text = StrangeText()
        integer = StrangeInteger()
        sfloat = StrangeFloat()
        array = StrangeArray()
        sdict = StrangeDictionary()
        for key in data:
            value = data[key]
            value_type = type(value)
            if value_type == str:
                text.load(value)
                out[key] = b'\x00' + text.dump_bytes()
            elif value_type == int:
                integer.load(value)
                out[key] = b'\x01' + integer.dump_bytes()
            elif value_type == float:
                sfloat.load(value)
                out[key] = b'\x02' + sfloat.dump_bytes()
            elif value_type == list:
                array.load(value)
                processed_value = StrangeObject.proccess_list_encode(value)
                array.load(processed_value)
                out[key] = b'\x03' + array.dump_bytes()
            elif value_type == dict:
                processed_value = StrangeObject.proccess_dict_encode(value)
                sdict.load(processed_value)
                out[key] = b'\x04' + sdict.dump_bytes()
            elif value_type == bytes:
                out[key] = b'\x05' + value
            elif value_type == bool:
                if value:
                    out[key] = b'\x06\x01'
                else:
                    out[key] = b'\x06\x00'
            else:
                raise TypeError(f'Unable to encode {value_type}')
        return out

    @staticmethod
    def proccess_list_encode(data: list) -> list:
        out = [None for _ in data]
        text = StrangeText()
        integer = StrangeInteger()
        sfloat = StrangeFloat()
        array = StrangeArray()
        sdict = StrangeDictionary()
        for key, value in enumerate(data):
            value_type = type(value)
            if value_type == str:
                text.load(value)
                out[key] = b'\x00' + text.dump_bytes()
            elif value_type == int:
                integer.load(value)
                out[key] = b'\x01' + integer.dump_bytes()
            elif value_type == float:
                sfloat.load(value)
                out[key] = b'\x02' + sfloat.dump_bytes()
            elif value_type == list:
                array.load(value)
                processed_value = StrangeObject.proccess_list_encode(value)
                array.load(processed_value)
                out[key] = b'\x03' + array.dump_bytes()
            elif value_type == dict:
                processed_value = StrangeObject.proccess_dict_encode(value)
                sdict.load(processed_value)
                out[key] = b'\x04' + sdict.dump_bytes()
            elif value_type == bytes:
                out[key] = b'\x05' + value
            elif value_type == bool:
                if value:
                    out[key] = b'\x06\x01'
                else:
                    out[key] = b'\x06\x00'
            else:
                raise TypeError(f'Unable to encode {value_type}')
        return out

    @staticmethod
    def decode(data: bytes) -> dict:
        dictionary = StrangeDictionary()
        dictionary.load_bytes(data)
        processed_data = StrangeObject.proccess_dict_decode(dictionary.dump())
        return processed_data

    @staticmethod
    def proccess_dict_decode(data: dict) -> dict:
        out = {}
        text = StrangeText()
        integer = StrangeInteger()
        sfloat = StrangeFloat()
        array = StrangeArray()
        sdict = StrangeDictionary()
        for key in data:
            value = data[key]
            if type(value) == bytes:
                value = bytearray(value)
                value_type = value[0]
                del value[0]
            else:
                continue
            if value_type == 0x00:
                text.load_bytes(value)
                out[key] = text.dump()
            elif value_type == 0x01:
                integer.load_bytes(value)
                out[key] = integer.dump()
            elif value_type == 0x02:
                sfloat.load_bytes(value)
                out[key] = sfloat.dump()
            elif value_type == 0x03:
                array.load_bytes(value)
                processed_data = StrangeObject.proccess_list_decode(array.dump())
                out[key] = processed_data
            elif value_type == 0x04:
                sdict.load_bytes(value)
                processed_data = StrangeObject.proccess_dict_decode(sdict.dump())
                out[key] = processed_data
            elif value_type == 0x05:
                out[key] = bytes(value)
            elif value_type == 0x06:
                if value == bytearray(b'\x01'):
                    out[key] = True
                else:
                    out[key] = False
            else:
                raise ValueError(f'Unable to decode {value_type}')
        return out

    @staticmethod
    def proccess_list_decode(data: list) -> list:
        out = [None for _ in data]
        text = StrangeText()
        integer = StrangeInteger()
        sfloat = StrangeFloat()
        array = StrangeArray()
        sdict = StrangeDictionary()
        for key, value in enumerate(data):
            if type(value) == bytes:
                value = bytearray(value)
                value_type = value[0]
                del value[0]
            else:
                continue
            if value_type == 0x00:
                text.load_bytes(value)
                out[key] = text.dump()
            elif value_type == 0x01:
                integer.load_bytes(value)
                out[key] = integer.dump()
            elif value_type == 0x02:
                sfloat.load_bytes(value)
                out[key] = sfloat.dump()
            elif value_type == 0x03:
                array.load_bytes(value)
                processed_data = StrangeObject.proccess_list_decode(array.dump())
                out[key] = processed_data
            elif value_type == 0x04:
                sdict.load_bytes(value)
                processed_data = StrangeObject.proccess_dict_decode(sdict.dump())
                out[key] = processed_data
            elif value_type == 0x05:
                out[key] = bytes(value)
            elif value_type == 0x06:
                if value == 1:
                    out[key] = True
                else:
                    out[key] = False
            else:
                raise ValueError(f'Unable to decode {value_type}')
        return out
